import { Inject, Injectable } from '@nestjs/common';
import { InterstMethod, Stage, LineFeeMethod } from '../entity/stage.entity';
import Decimal from 'decimal.js';
import { AppException } from '@libs/exception';
import { StageInterest } from '../entity/stageInterest.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { Project } from '../entity/project.entity';
import { StageFunding, StageFundingMinusType, StageFundingPlusType } from '../entity/stageFunding.entity';

@Injectable()
export class InterestFeeService {
  constructor(
    @InjectRepository(StageInterest)
    private stageInterestRepository: Repository<StageInterest>,
    @InjectRepository(StageFunding)
    private stageFundingRepository: Repository<StageFunding>,
  ) {}

  getDaysInLoanMonth(loanStartDate: Date, currentDate: Date): number {
    // Calculate the current month start and end based on loan period
    let nextMonthStart = new Date(loanStartDate);
    let monthStart, monthEnd ;
    const startDay = loanStartDate.getDate();
    const endDay = startDay - 1;

    while (nextMonthStart <= currentDate) {
      monthStart = nextMonthStart
      monthEnd = new Date(nextMonthStart);
      
      const daysInMonth = new Date(monthStart.getFullYear(), monthStart.getMonth() + 2, 0).getDate();
      if (daysInMonth < endDay) {
        monthEnd.setMonth(monthEnd.getMonth() + 1);
        monthEnd.setDate(0);
      } else {
        const currentStartDay = monthStart.getDate();
        if (endDay == 0) {
          monthEnd.setMonth(monthEnd.getMonth() + 1);
          monthEnd.setDate(endDay);
        } else if (currentStartDay >= endDay) {
          monthEnd.setDate(endDay);
          monthEnd.setMonth(monthEnd.getMonth() + 1);
        } else {
          monthEnd.setDate(endDay);
        }
      }
      nextMonthStart = new Date(monthEnd);
      nextMonthStart.setDate(nextMonthStart.getDate() + 1);
    }

    // Calculate days in this loan month
    const timeDiff = monthEnd.getTime() - monthStart.getTime();
    return Math.ceil(timeDiff / (1000 * 60 * 60 * 24)) + 1;
  }

  calculateDailyInterest(interestMethod: string, loanAmount: Decimal, rate: Decimal, startDate: Date, currentDate: Date): Decimal {
    if (interestMethod === InterstMethod.MONTHLY) {
      const daysInMonth = this.getDaysInLoanMonth(startDate, currentDate);
      return loanAmount.mul(rate).div(12).div(daysInMonth);
    } else if (interestMethod === InterstMethod.DAILY) {
      return loanAmount.mul(rate).div(365);
    } else {
      throw new AppException('无效的计息方式', { httpCode: 400 });
    }
  }

  calculateMonthsBetweenDates(startDate: Date, endDate: Date): number {
    const startYear = startDate.getFullYear();
    const startMonth = startDate.getMonth();
    const endYear = endDate.getFullYear();
    const endMonth = endDate.getMonth();

    return (endYear - startYear) * 12 + (endMonth - startMonth);
  }

  calculateMonthlyLineFee(lineFeeMethod: string, facility: Decimal, rate: Decimal, startDate: Date, currentDate: Date): Decimal {
    if (lineFeeMethod === InterstMethod.MONTHLY) {
      return facility.mul(rate).div(12)
    } else if (lineFeeMethod === InterstMethod.DAILY) {
      const daysInMonth = this.getDaysInLoanMonth(startDate, currentDate);
      return facility.mul(rate).div(365).mul(daysInMonth);
    } else {
      throw new AppException('无效的计息方式', { httpCode: 400 });
    }
  }

  async updateDailyStageInterest(stage: Stage, rate: Decimal) {
    // Delete existing interests
    await this.stageInterestRepository.delete({ stageId: stage.id });
    const { loanStartDate, loanRepaymentDate } = stage;
    const daysInPeriod = Math.ceil((loanRepaymentDate.getTime() - loanStartDate.getTime()) / (1000 * 60 * 60 * 24)) + 1;

    const interests = [];
    let currentDate = new Date(loanStartDate);

    for (let i = 0; i < daysInPeriod; i++) {
      const dailyInterest = this.calculateDailyInterest(stage.interestMethod, stage.loanAmount, rate, stage.loanStartDate, currentDate);

      interests.push(
        this.stageInterestRepository.create({
          stageId: stage.id,
          borrowerId: stage.project.borrowerId,
          date: new Date(currentDate),
          money: dailyInterest,
        }),
      );

      currentDate.setDate(currentDate.getDate() + 1);
    }

    await this.stageInterestRepository.save(interests);
  }

  async updateMonthlyLineFee(project: Project, stage: Stage) {
    // Delete existing interests
    await this.stageFundingRepository.delete({ stageId: stage.id, type: In([StageFundingMinusType.InterestLineFee, StageFundingPlusType.InterestLineFeePayment ]) });
    if (!stage.lineFeeRate||stage.lineFeeRate.eq(0)) {
      return
    }
    const { loanStartDate, loanRepaymentDate } = stage;
    const monthsInPeriod = this.calculateMonthsBetweenDates(loanStartDate, loanRepaymentDate);

    const fundings = [];
    let currentDate = new Date(loanStartDate);
    for (let i = 0; i < monthsInPeriod; i++) {
      currentDate.setMonth(loanStartDate.getMonth() + i);
      let startDate = new Date(currentDate);
      let endDate = new Date(startDate);
      endDate.setMonth(startDate.getMonth() + 1)
      endDate.setDate(endDate.getDate() - 1);
      endDate = endDate.getTime() > loanRepaymentDate.getTime() ? loanRepaymentDate : endDate;
      const monthlyLineFee = this.calculateMonthlyLineFee(stage.lineFeeMethod, project.facilityLimit, stage.lineFeeRate, stage.loanStartDate, currentDate);
      
      fundings.push(
        this.stageFundingRepository.create({
          stageId: stage.id,
          name: StageFundingMinusType.InterestLineFee,
          type: StageFundingMinusType.InterestLineFee,
          value: monthlyLineFee,
          startDate: startDate,
          endDate: endDate,
          isDoubleEntry: false,
          isSettlement: false,
        }),
      );
      fundings.push(
        this.stageFundingRepository.create({
          stageId: stage.id,
          name: StageFundingPlusType.InterestLineFeePayment,
          type: StageFundingPlusType.InterestLineFeePayment,
          value: monthlyLineFee,
          startDate: startDate,
          endDate: endDate,
          isDoubleEntry: false,
          isSettlement: false,
        }),
      );

    }
    if (fundings.length === 0) {
      return
    }
    await this.stageFundingRepository.save(fundings);
  }
}
